package com.capgemini.cn.authority.core.authentication;

import com.capgemini.cn.authority.core.security.SecurityProfile;
import com.capgemini.cn.authority.data.entity.Asserts;
import com.capgemini.cn.authority.data.entity.AssertsGroup;
import com.capgemini.cn.authority.data.entity.Authorities;
import com.capgemini.cn.authority.data.entity.Roles;
import com.capgemini.cn.authority.data.repository.AssertsRepository;
import com.capgemini.cn.authority.data.repository.AuthoritiesRepository;
import lombok.Getter;
import lombok.Setter;
import org.pac4j.core.authorization.authorizer.Authorizer;
import org.pac4j.core.context.J2EContext;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.exception.HttpAction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * <p>检查用户的角色和权限</p>
 * Created at 2017/8/31
 *
 * @author Andriy
 */
@Component(value = CheckRoleAndPermissionAuthorizer.COMPONENT_NAME)
public class CheckRoleAndPermissionAuthorizer implements Authorizer<SecurityProfile> {

    public static final String COMPONENT_NAME = "checkRoleAndPermissionAuthorizer";

    @Getter
    @Setter
    @Autowired
    private AssertsRepository assertsRepository;

    @Getter
    @Setter
    @Autowired
    private AuthoritiesRepository authoritiesRepository;

    @Transactional
    @Override
    public boolean isAuthorized(WebContext context, List<SecurityProfile> profiles) throws HttpAction {
        Assert.notNull(context, "参数WebContext不能为空，无法正常进行授权判断！");
        Assert.notNull(profiles, "参数List<SecurityProfile>不能为空，无法正常进行授权判断！");
        boolean result = false;
        // 获取客户端访问的URI地址
        String reqPath = context.getPath();
        Assert.notNull(reqPath, "无法获取到客户端的请求路径，故而不能正常进行授权判断！");
        // 根据获取到的URI值查询到对应的角色和权限
        Asserts asserts = this.assertsRepository.findByAssertValue(reqPath);
        // 对请求地址做特殊处理，兼容一些框架将(eg. 恶心的Dubbox就干这种生儿子没屁眼的事情)项目上下文直接放到Path中而不是Request的contextPath
        // 如果根据原始请求地址查询不到资源并且在Request的contextPath中的上下文为空，则开始对获取到的请求地址做处理
        if (ObjectUtils.isEmpty(asserts) && StringUtils.isEmpty(((J2EContext) context).getRequest().getContextPath())) {
            int firstSlash = reqPath.indexOf("/");
            // 如果第一个斜杠的开始位置在首位且余下字符内容仍然包含斜杠，则假设第一个斜杠和第二个斜杠之间的内容为项目上下文
            if (0 == firstSlash) {
                // 移除掉原始请求地址中的首个斜杠，然后开始判断余下的字符中是否还包含斜杠
                final String tmpPath = reqPath.substring(firstSlash + 1, reqPath.length());
                // 斜杠出现的第一个位置和最后一次出现的位置不一致，则表示存在多个斜杠
                if (tmpPath.contains("/")) {
                    final String finalPath = tmpPath.substring(tmpPath.indexOf("/"), tmpPath.length());
                    asserts = this.assertsRepository.findByAssertValue(finalPath);
                }
            }
        }

        if (!ObjectUtils.isEmpty(asserts)) {
            AssertsGroup assertsGroup = asserts.getAssertsGroup();
            if (ObjectUtils.isEmpty(assertsGroup))
                throw HttpAction.unauthorized("当前访问资源未被正常分配至资源组中，无法进行授权访问！", context, context.getServerName(), reqPath);
            // 获得当前资源对应的权限列表
            List<Authorities> authoritiesList = assertsGroup.getAuthorities();
            // 声明用以存放所拥有权限对应的角色信息
            Map<String, Roles> ownedRoles = new HashMap<>();
            // 根据每个权限项获取对应的角色信息
            for (Authorities authority : authoritiesList) {
                // 获取该权限对应的角色列表
                List<Roles> authorityRoleList = authority.getRoles();
                for (Roles role : authorityRoleList) {
                    // 如果角色Map中不存在重复的角色信息则添加至Map中
                    if (!ownedRoles.containsKey(role.getId())) {
                        ownedRoles.put(String.valueOf(role.getId()), role);
                    }
                }
            }
            // 从这里开始对用户的权限进行校验
            // 遍历profiles参数，校验用户是否具有访问当前URI的角色和权限,先校验角色再校验权限
            for (SecurityProfile profile : profiles) {
                // 先校验角色，确保用户拥有对应的角色信息
                Set<String> profileRoles = profile.getRoles();
                for (String roleId : profileRoles) {   // 从profile中获取到的权限信息都是数据的主键ID值
                    // 先判断资源对应的角色信息是否在用户的角色列表中，如果不在用户角色列表中则表示没有权限对应的角色授权
                    // 反之则表示当前用户有被授予与资源分配相同的角色
                    if (ownedRoles.containsKey(roleId)) {
                        // 然后校验资源对应的权限信息是否在用户的权限列表中
                        Set<String> profileAuthorities = profile.getPermissions();
                        for (String authorityCode : profileAuthorities) {
                            // 赋予给用户的权限信息都是权限编码，所以这里根据权限编码查询对应的权限信息
                            Authorities authority = this.authoritiesRepository.findByAuthorityCode(authorityCode);
                            if (!ObjectUtils.isEmpty(authority)) {  // 判断当前用户的授权信息是否存在有效
                                List<Roles> authorityRoles = authority.getRoles();  // 获得权限对应的角色信息
                                for (Roles role : authorityRoles) {
                                    // 比较查询到的权限所属角色是否在用户所分配的角色信息中
                                    if (roleId.equals(String.valueOf(role.getId()))) {
                                        // TODO 除了校验角色是否拥有相应权限之外，还需要根绝不同的权限类型完成个性化校验逻辑
                                        result = true;
                                        break;
                                    }
                                }
                            }
                            // 如果结果为授权成功则直接跳出
                            if (result) break;
                        }
                    }
                    // 如果结果为授权成功则直接跳出
                    if (result) break;
                }
                // 如果结果为授权成功则直接跳出
                if (result) break;
            }
        }
        return result;
    }
}
